package at.fhhgb.mc.Aufgabe01;

import at.fhhgb.mc.Aufgabe01.DLNode;

/**
 * Excercise 2 DoubleLinkedList Extended
 * 
 * @author Konrad
 * 
 */
public class DoubleLinkedList {

	private DLNode head, tail;

	/**
	 * Initializies the list with an element
	 */
	public DoubleLinkedList() {
		this.head = null;
		this.tail = null;
	}

	/**
	 * Initializes the list with an other list, in reverse order, necessary for the stack
	 * 
	 * @param other
	 *            Other list that will be pushed
	 */
	public DoubleLinkedList(DoubleLinkedList other) {
		if (other.head != null) {
			DLNode current = other.tail;
			do {
				this.pushFront(current.val);
				current = current.prev;
			} while (current != null);
		}
	}

	/**
	 * Finalize, calls clear
	 */
	protected void finalize() {
		this.clear();
	}

	/**
	 * Goes through every element of the list and sets its pointers to null
	 */
	public void clear() {
		if (this.head != null) {
			DLNode current = this.head;
			DLNode current2 = this.head.next;

			do {
				current.next = null;
				current.prev = null;

				current = current2;
				if (current != null) {
					current2 = current2.next;
				}
			} while (current != null);
			current = null;
			current2 = null;
		}
		this.head = null;
		this.tail = null;

	}

	/**
	 * Pushes an other list to the front of an existing list
	 * 
	 * @param other
	 *            Other list that will be pushed
	 */
	public void pushFront(DoubleLinkedList other) {
		DLNode current = other.tail;
		if (this.tail != other.tail) {
			do {
				this.pushFront(current.val);
				current = current.prev;
			} while (current != null);
		}
	}

	/**
	 * Pushes an integer value to the front of the list by creating a new node
	 * 
	 * @param val
	 *            Value that will be pushed
	 */
	public void pushFront(int val) {
		DLNode node = new DLNode();
		node.next = this.head;
		if (node.next != null) {
			this.head.prev = node;
		}
		this.head = node;
		if (this.tail == null) {
			this.tail = node;
		}
		node.val = val;
	}

	/**
	 * Pushes an other list to the back of an existing list
	 * 
	 * @param other
	 *            Other list that will be pushed
	 */
	public void pushBack(DoubleLinkedList other) {
		DLNode current = other.head;
		if (this.head != other.head) {
			do {
				this.pushBack(current.val);
				current = current.next;
			} while (current != null);
		}
	}

	/**
	 * Returns the head of the current list
	 * @return Returns the head of the current list
	 */
	public DLNode getHead() {
		return this.head;
	}

	/**
	 * Returns the tail of the current list
	 * @return Returns the tail of the current list
	 */
	public DLNode getTail() {
		return this.tail;
	}

	protected void setTail(DLNode newTail){
		this.tail = newTail;
	}
	
	
	/**
	 * Pushes an integer value to the front of the list by creating a new node
	 * 
	 * @param val
	 *            Value that will be pushed
	 */
	public void pushBack(int val) {
		DLNode node = new DLNode();

		node.prev = this.tail;
		if (node.prev != null) {
			this.tail.next = node;
		}

		this.tail = node;
		if (this.head == null) {
			this.head = node;
		}
		node.val = val;
	}

	/**
	 * Returns the first element of the list and deletes it
	 * 
	 * @return First element of the list or Integer.MIN_VALUE if empty
	 */
	public int popFront() {
		int temp;
		DLNode tempNode;

		if (this.head != null) {
			temp = this.head.val;
			tempNode = this.head.next;
			if (this.head.next != null) {
				this.head.next.prev = null;
			} else if (this.head == this.tail) {
				this.tail = tempNode;
			}
			this.head = tempNode;
		} else {
			temp = Integer.MIN_VALUE;
		}
		return temp;
	}

	/**
	 * Returns the first element of the list without deleting it
	 * 
	 * @return First element of the list or Integer.MIN_VALUE if empty
	 */
	public int peekFront() {
		int temp;
		if (this.head != null) {
			temp = this.head.val;
		} else {
			temp = Integer.MIN_VALUE;
		}
		return temp;
	}

	/**
	 * Returns the last element of the list and deletes it
	 * 
	 * @return Last element of the list or Integer.MIN_VALUE if empty
	 */
	public int popBack() {
		int temp;
		DLNode tempNode;

		if (this.tail != null) {
			temp = this.tail.val;
			tempNode = this.tail.prev;
			if (this.tail.prev != null) {
				this.tail.prev.next = null;
			}
			this.tail = tempNode;
		} else {
			temp = Integer.MIN_VALUE;
		}
		return temp;
	}

	/**
	 * Returns the last element of the list without deleting it
	 * 
	 * @return Last element of the list or Integer.MIN_VALUE if empty
	 */
	public int peekBack() {
		int temp;
		if (this.tail != null) {
			temp = this.tail.val;
		} else {
			temp = Integer.MIN_VALUE;
		}
		return temp;
	}

	/**
	 * Clones an existing list and returns it
	 */
	public DoubleLinkedList clone() {
		DoubleLinkedList clonedList = new DoubleLinkedList();

		clonedList.pushBack(this);

		return clonedList;
	}

	/**
	 * Returns the number of stored elements
	 * 
	 * @return Number of stored elements
	 */
	public int elements() {
		int counter = 0;
		DLNode current = this.head;

		counter = elementsHelper(current, counter);

		return counter;
	}

	/**
	 * Helper method to go recursively through every element
	 * 
	 * @param current
	 *            Current Node
	 * @param counter
	 *            current counter of elements
	 * @return
	 */
	private int elementsHelper(DLNode current, int counter) {

		if (current != null) {
			counter += 1;
			current = current.next;
			counter = elementsHelper(current, counter);
		}

		return counter;
	}

	/**
	 * Compares two lists an returns true or false
	 * 
	 * @param other
	 *            Other list that will be compared
	 * @return Returns true or false
	 */
	public boolean equals(DoubleLinkedList other) {
		DLNode current1 = this.head;
		DLNode current2 = other.head;

		if (this.elements() != other.elements()) {
			return false;
		}

		do {
			if (current1.val != current2.val) {
				return false;
			}
			current1 = current1.next;
			current2 = current2.next;
		} while (current1 != null);

		return true;
	}

	/**
	 * Reverses the list
	 */
	public void reverse() {
		if (this.tail != null) {
			DLNode current = this.tail;
			DLNode tempNode;

			do {
				if (current.next == null) {
					current.next = current.prev;
					current.prev = null;
					this.head = current;
				} else {
					tempNode = current.prev;
					current.prev = current.next;
					current.next = tempNode;
				}
				if (current.next == null) {
					this.tail = current;
				} else {
					current = current.next;
				}

			} while (this.tail != current);
		}
	}

	/**
	 * Creates a string of the list
	 */
	public String toString() {
		StringBuffer listString = new StringBuffer();
		DLNode current = this.head;

		do {
			listString.append(current.val + "|");
			current = current.next;
		} while (current != null);

		return listString.toString();
	}

	/**
	 * Searches the list for a value
	 * 
	 * @param val
	 *            Value that will be searched
	 * @return Returns true or false
	 */
	public boolean search(int val) {
		DLNode current = this.head;
		do {
			if (current.val == val) {
				return true;
			}
			current = current.next;
		} while (current != null);

		return false;
	}
}
